/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.grid.util;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * <p>Class that contains all data related to grid, such as set of filters, data that must be rendered, etc.</p>
 * <p><a href="Grid.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.11 $ $Date: 2006/08/03 10:08:22 $
 */
public class Grid implements Serializable {

	// ~ Instance variable

	/**
	 * Name of this grid
	 */
	protected String name;

	/**
	 * Map containing filters for fields.
	 */
	protected Map filters = Collections.synchronizedMap(new HashMap());

	/**
	 * List of fields to sort by
	 */
	protected List sortFields = Collections.synchronizedList(new LinkedList());

	/**
	 * Total number of elements in grid
	 */
	protected Integer total = new Integer(0);

	/**
	 * Number of elements displayed on each page in grid
	 */
	protected Integer pageSize;

	/**
	 * Map of queryParameters to put in query
	 */
	protected Map queryParameters = Collections.synchronizedMap(new HashMap());

	/**
	 * Creates new instance of Grid
	 */
	public Grid(String name) {
		this.name = name;
	}

	// ~ Business methods

    /**
     * Returns grid name
     *
     * @return grid name
     */
	public String getName() {
		return name;
	}

    /**
     * Returns mapping from field names to their filters
     *
     * @return mapping from field names to their filters
     */
	public Map getFilters() {
		return filters;
	}

    /**
     * Sets mapping from field names to their filters
     *
     * @param filters mapping from field names to their filters
     */
	public void setFilters(Map filters) {
		this.filters = filters;
	}

	/**
	 * Adds new filter to map
	 *
	 * @param filter Filter instance
	 */
	public void addFilter(GridFilter filter) {
		this.filters.put(filter.getFieldName(), filter);
	}

	/**
	 * Removes specified filter from grid
	 *
	 * @param filter Filter to be removed
	 */
	public void removeFilter(GridFilter filter) {
		this.filters.remove(filter.getFieldName());
	}

	/**
	 * Removes filter associated with specified field name
	 *
	 * @param fieldName Field name to remove filter from
	 */
	public void removeFilter(String fieldName) {
		this.filters.remove(fieldName);
	}

	/**
	 * Removes all filters associated with this instance
	 */
	public void clearFilters() {
		filters.clear();
	}

	/**
	 * Returns filter associated with specified field
	 *
	 * @param fieldName Name of field to get filter for
	 * @return Filter associated with specified field
	 */
	public GridFilter getFilterByFieldName(String fieldName) {
		return (GridFilter) filters.get(fieldName);
	}

	/**
	 * Returns <code>true</code>, if filter for specified field has already been enabled
	 * and false otherwise
	 *
	 * @param fieldName Field name to search filter for
	 * @return True, if filter for specified field has already been enabled and false otherwise
	 */
	public boolean containsFilter(String fieldName) {
		return getFilterByFieldName(fieldName) != null;
	}


	/**
	 * Returns <code>true</code> if filter is enabled.
	 * Filter is enabled if it is exists for this field and if this filter
	 * is instance of SetFilter, it must contain non-empty list of selected values.
	 *
	 * @param fieldName Field name to search filter for
	 * @return True if filter for specified field is enabled
	 */
	public boolean isFilterEnabled(String fieldName) {
		boolean filterEnabled = false;
		if ( getFilterByFieldName(fieldName) != null ) {
			filterEnabled = true;
		}
		return filterEnabled;
	}

    /**
     * Returns list of fields to sort by
     *
     * @return list of fields to sort by
     */
	public List getSortFields() {
		return sortFields;
	}

    /**
     * Sets list of fields to sort by
     *
     * @param sortFields list of fields to sort by
     */
	public void setSortFields(List sortFields) {
		this.sortFields = Collections.synchronizedList(sortFields);
	}


	/**
	 * Gets sort field associated with specified field name.
	 * If no field was found returns null
	 *
	 * @param fieldName Name of field to search sort field associated with
	 * @return Sort field or null if nothing was found
	 */
	public SortField getSortFieldByFieldName(String fieldName) {
		SortField sortField = new SortField(fieldName, SortField.ORDER_ASCENDING);
		int index = sortFields.indexOf(sortField);
		if ( index == -1 ) {
			sortField = new SortField(fieldName, SortField.ORDER_DESCENDING);
			index = sortFields.indexOf(sortField);
		}
		if ( index != -1 ) {
			// sort field has been found
			return (SortField) sortFields.get(index);
		} else {
			return null;
		}
	}

	/**
	 * Adds new sort field to the begin of the list of sort fields
	 *
	 * @param sortField Sort field to add
	 */
	public void addSortField(SortField sortField) {
		// remove sort field (if exists) associated with the field name of given sort field
		removeSortField(sortField.getFieldName());
		LinkedList tmp = new LinkedList(sortFields);
		tmp.addFirst(sortField);
		this.sortFields = Collections.synchronizedList(tmp);
	}

	/**
	 * Returns true if specified sort field is contained in list of sort fields
	 * and false otherwise
	 *
	 * @param sortField Sort fields to search in list
	 * @return True if specified sort field is contained in list of sort fields and false otherwise
	 */
	public boolean containsSortField(SortField sortField) {
		return sortFields.contains(sortField);
	}

	/**
	 * Returns true if sorting by field with specified name has been enabled.
	 * Sorting order is unimportant.
	 *
	 * @param fieldName
	 * @return True if sorting by field with specified name has been enabled and false otherwise.
	 */
	public boolean containsSortField(String fieldName) {
		return sortFields.contains(new SortField(fieldName, SortField.ORDER_ASCENDING)) || sortFields.contains(new SortField(fieldName, SortField.ORDER_DESCENDING));
	}

	/**
	 * Removes specified sort field from the list if it is exists
	 *
	 * @param sortField Sort field to remove form list
	 */
	public void removeSortField(SortField sortField) {
		sortFields.remove(sortField);
	}

	/**
	 * Removes sort field associated with specified field name
	 *
	 * @param fieldName Name of field to remove from list of sort fields
	 */
	public void removeSortField(String fieldName) {
		sortFields.remove(new SortField(fieldName, SortField.ORDER_ASCENDING));
		sortFields.remove(new SortField(fieldName, SortField.ORDER_DESCENDING));
	}

    /**
     * Sets total number of elements in grid (as Integer)
     *
     * @param total number to set
     */
	public void setTotal(Integer total) {
		this.total = total;
	}

    /**
     * Sets total number of elements in grid
     *
     * @param total number to set
     */
	public void setTotal(int total) {
		setTotal(new Integer(total));
	}

    /**
     * Returns total number of elements in grid
     *
     * @return number of elements
     */
	public Integer getTotal() {
		return total;
	}

    /**
     * Increments number of elements in grid to specified value (in Integer)
     *
     * @param total added value
     */
	public void addTotal(Integer total) {
		this.total = new Integer(this.total.intValue() + total.intValue());
	}

    /**
     * Increments number of elements in grid to specified value
     *
     * @param total added value
     */
	public void addTotal(int total) {
		addTotal(new Integer(total));
	}

    /**
     * Returns number of elements displayed on each page in grid
     *
     * @return number of elements displayed on each page
     */
	public Integer getPageSize() {
		return pageSize;
	}

    /**
     * Sets number of elements displayed on each page in grid
     *
     * @param pageSize number of elements displayed on each page to set
     */
	public void setPageSize(Integer pageSize) {
		this.pageSize = pageSize;
	}

    /**
     * Returns map of query parameters to put in query
     *
     * @return map of query parameters
     */
	public Map getQueryParameters() {
		return queryParameters;
	}

    /**
     * Sets map of query parameters to put in query
     *
     * @param queryParameters map of query parameters to set
     */
	public void setQueryParameters(Map queryParameters) {
		this.queryParameters = queryParameters;
	}

	/**
	 * Adds new parameter
	 *
	 * @param name  Key to associate parameter with
	 * @param value Parameter value
	 */
	public void addQueryParameter(String name, Object value) {
		this.queryParameters.put(name, value);
	}

	/**
	 * Removes parameter associated with specified name
	 *
	 * @param name Key of parameter to remove
	 * @return Parameter value
	 */
	public Object removeQueryParameter(String name) {
		return this.queryParameters.remove(name);
	}

	/**
	 * Clears all query parameters
	 */
	public void clearQueryParameters() {
		this.queryParameters = Collections.synchronizedMap(new HashMap());
	}

	/**
	 * Gets list of conditions combined with AND copulas for iterator with specified ID.
	 * WHERE keyword is not added
	 *
	 * @return WHERE clause
	 */
	public String getWhereClause(String iteratorId) {
		Log log = LogFactory.getLog(Grid.class);

		String whereClause = new String();

		List filters = new ArrayList(this.filters.values());
		for ( int i = 0; i < filters.size(); i++ ) {
			GridFilter filter = (GridFilter) filters.get(i);
			String clause = filter.getClause(iteratorId);
			if ( clause != null ) {
				whereClause += clause;
			}
			if ( i < filters.size() - 1 ) {
				GridFilter nextFilter = (GridFilter) filters.get(i + 1);
				if ( nextFilter.getClause(iteratorId) != null ) {
					whereClause += " and ";
				}
			}
		}
		if ( log.isDebugEnabled() ) {
			log.debug("Created WHERE clause: " + whereClause);
		}
		return whereClause;
	}

	/**
	 * Returns WHERE clause with iterator ID ignored
	 *
	 * @return WHERE clause
	 */
	public String getWhereClause() {
		return getWhereClause(null);
	}

	/**
	 * Gets list of fields to sort by with order for each field for iterator with specified ID.
	 * ORDER BY keyword is not added
	 *
	 * @return ORDER BY clause
	 */
	public String getOrderByClause(String iteratorId) {

		Log log = LogFactory.getLog(Grid.class);

		String orderByClause = new String();
		for ( int i = 0; i < sortFields.size(); i++ ) {
			SortField sortField = (SortField) sortFields.get(i);

			String clause = sortField.getClause(iteratorId);
			if ( clause != null ) {
				orderByClause += clause;
			}
			if ( i < sortFields.size() - 1 ) {
				SortField nextField = (SortField) sortFields.get(i + 1);
				if ( nextField.getClause(iteratorId) != null ) {
					orderByClause += ", ";
				}
			}
		}
		if ( log.isDebugEnabled() ) {
			log.debug("Created ORDER BY clause: " + orderByClause);
		}
		return orderByClause;
	}

	/**
	 * Returns ORDER BY clause with iterator ID ignored
	 *
	 * @return ORDER BY clause
	 */
	public String getOrderByClause() {
		return getOrderByClause(null);
	}

	public String toString() {
		return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
	}

	/**
	 * Returns sort field with specified index, if specified rows iterator is supported by field. Otherwise returns <code>null</code>.
     * 
	 * @param index  Index of sort field to return
	 * @param iteratorId Rows iterator ID to check. Can be <code>null</code>. In this case, no check will be performed and field will be returned.
	 * @return Returns sort field with specified index, if specified rows iterator is supported by field.
	 */
	public SortField getSortFieldByIndex(int index, String iteratorId) {
		SortField sortField = (SortField) sortFields.get(index);
		List rowIterators = sortField.getRowIterators();
		if ( (iteratorId == null) || (rowIterators.size() == 0) || (rowIterators.contains(iteratorId)) ) {
			return sortField;
		} else {
			return null;
		}
	}
}
